package com.shockweb.rpc.spring;


import java.io.IOException;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.context.annotation.ScopeMetadata;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;

import com.shockweb.common.log.LogManager;
import com.shockweb.service.data.ServiceStatus;

/**
 * 类注解扫描器
 * @see ClassPathBeanDefinitionScanner
 * 
 * @author 彭明华
 * 2018年1月26日 创建
 */
public final class Scanner extends ClassPathBeanDefinitionScanner {
	/**
	 * 构造方法
	 * @param registry
	 */
	public Scanner(BeanDefinitionRegistry registry) {
		super(registry);
	}
	
	/**
	 * 注册过滤器
	 */
	@Override
	public void registerDefaultFilters() {
		this.addIncludeFilter(new AnnotationTypeFilter(ShockWebRemote.class));
	}

	
	/**
	 * 获取ClassPathBeanDefinitionScanner的私有属性scopeMetadataResolver
	 * @return
	 */
	private ScopeMetadataResolver getScopeMetadataResolver(){
		try{
			Class<?> classType = ClassPathBeanDefinitionScanner.class;
			Field field = classType.getDeclaredField("scopeMetadataResolver");
			field.setAccessible(true);
			return (ScopeMetadataResolver)field.get(this);
		}catch(Exception ex){
			LogManager.errorLog(this.getClass(), ex);
		}
		return null;
	}
	
	
	/**
	 * 获取ClassPathBeanDefinitionScanner的私有属性beanNameGenerator
	 * @return
	 */
	private BeanNameGenerator getBeanNameGenerator(){
		try{
			Class<?> classType = ClassPathBeanDefinitionScanner.class;
			Field field = classType.getDeclaredField("beanNameGenerator");
			field.setAccessible(true);
			return (BeanNameGenerator)field.get(this);
		}catch(Exception ex){
			LogManager.errorLog(this.getClass(), ex);
		}
		return null;
	}

	/**
	 * 获取ClassPathBeanDefinitionScanner的私有属性resourcePatternResolver
	 * @return
	 */
	private ResourcePatternResolver getResourcePatternResolver(){
		try{
			Class<?> classType = ClassPathScanningCandidateComponentProvider.class;
			Field field = classType.getDeclaredField("resourcePatternResolver");
			field.setAccessible(true);
			return (ResourcePatternResolver)field.get(this);
		}catch(Exception ex){
			LogManager.errorLog(this.getClass(), ex);
		}
		return null;
	}
	
	/**
	 * 获取ClassPathBeanDefinitionScanner的私有属性resourcePattern
	 * @return
	 */
	private String getResourcePattern(){
		try{
			Class<?> classType = ClassPathScanningCandidateComponentProvider.class;
			Field field = classType.getDeclaredField("resourcePattern");
			field.setAccessible(true);
			return (String)field.get(this);
		}catch(Exception ex){
			LogManager.errorLog(this.getClass(), ex);
		}
		return null;
	}
	
	/**
	 * Determine whether the given class does not match any exclude filter
	 * and does match at least one include filter.
	 * @param metadataReader the ASM ClassReader for the class
	 * @return whether the class qualifies as a candidate component
	 */
	protected boolean isCandidateComponent(ScannedGenericBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())) ||
				(metadata.isInterface() && metadata.hasAnnotation(ShockWebRemote.class.getName()))
				));
	}

	/**
	 * Scan the class path for candidate components.
	 * @param basePackage the package to check for annotated classes
	 * @return a corresponding Set of autodetected bean definitions
	 */
	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + getResourcePattern();
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
						if (isCandidateComponent(metadataReader)) {
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);

							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}
	
	/**
	 * 扫描方法
	 * @see ClassPathBeanDefinitionScanner#doScan(String... basePackages)
	 */
	@Override
	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = getScopeMetadataResolver().resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = getBeanNameGenerator().generateBeanName(candidate, getRegistry());
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				ServiceStatus.getServices().add(beanName);
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder = applyScopedProxyMode(scopeMetadata, definitionHolder, getRegistry());
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, getRegistry());

					GenericBeanDefinition definition = (GenericBeanDefinition) definitionHolder.getBeanDefinition();
					definition.getPropertyValues().add("innerClassName", definition.getBeanClassName());
					definition.setBeanClass(RemoteFactoryBean.class);
				}
			}
		}
		return beanDefinitions;
	}

	/**
	 * @see AnnotationConfigUtils#applyScopedProxyMode(metadata, definition, registry)
	 * @param metadata
	 * @param definition
	 * @param registry
	 * @return
	 */
	private static BeanDefinitionHolder applyScopedProxyMode(
			ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

		ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
		if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
			return definition;
		}
		boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
		return ScopedProxyUtils.createScopedProxy(definition, registry, proxyTargetClass);
	}

	

}
